use crate::http::{HttpMethod, HttpRequest, HttpResponse};
use crate::http::{HttpRequestBuilder, IntoBytes};
use crate::client::error::ClientError;
use tokio::net::TcpStream;
use tokio::io::AsyncWriteExt;
use sha1::{Digest, Sha1};
use base64::{engine::general_purpose, Engine as _};

use super::error::ClientBuildHttpRequest;
use super::websocket::WebSocketImpl;
use super::Client;

pub struct HttpImpl {
    stream: TcpStream,
    keep_alive: bool,
}

impl Client<HttpImpl> {
    pub async fn connect(addr: &str) -> Result<Self, ClientError> {
        let stream = TcpStream::connect(addr).await?;
        Ok( Self { implement: HttpImpl { stream, keep_alive: true } } )
    } 

    pub async fn send(&mut self, request: HttpRequest) -> Result<HttpResponse, ClientError> {
        let mut request = request;
        if request.headers.connection.is_none() {
            request.headers.set_keep_alive(true);
        }

        // Send and Recive
        let bytes = request.to_bytes();
        self.implement.stream.write_all(&bytes).await?;
        let response = HttpResponse::receive_from(&mut self.implement.stream)
            .await?
            .ok_or(ClientError::ConnectionClosed)?;

        if !response.headers.is_keep_alive() {
            self.implement.keep_alive = false;
        }

        Ok(response)
    }

    pub async fn upgrade_websocket(mut self) -> Result<Client<WebSocketImpl>, ClientError> {
        // Gnerate a Sec-WebSockey-Key
        let key_bytes: [u8; 16] = rand::random();
        let sec_key = general_purpose::STANDARD.encode(&key_bytes);
        
        // Build a request
        let request = HttpRequest::builder()
            .method(HttpMethod::Get)
            .path("/ws").unwrap()
            .connection("Upgrade")
            .upgrade("websocket")
            .sec_websocket_version("13")
            .sec_websocket_key(&sec_key)
            .build()?;

        request.send_to(&mut self.implement.stream).await?;

        // Wait server
        let response = HttpResponse::receive_from(&mut self.implement.stream)
            .await?
            .ok_or(ClientError::ConnectionClosed)?;
        
        // Check 
        if response.line.status_code != 101 {
            return Err(ClientError::InvalidWebSocketUpgrade(format!("Status code '{}' invalid", response.line.status_code)));
        }

        let headers = &response.headers;
        if headers.connection.as_ref().map(|s| s.as_str()) != Some(&"Upgrade".to_string()) {
            return Err(ClientError::InvalidWebSocketUpgrade("No 'Upgrade' or invalid value".into()));
        }

        if headers.upgrade.as_ref().map(|s| s.as_str()) != Some(&"websocket".to_string()) {
            return Err(ClientError::InvalidWebSocketUpgrade("No 'Upgrade' or invalid value".into()));
        }

        let accept = match headers.sec_websocket_accept.as_ref().map(|s| s.as_str()) {
            None => return Err(ClientError::InvalidWebSocketUpgrade("No 'Sec-WebSocket-Accept'".into())),
            Some(v) => v,
        };

        let mut hasher = Sha1::new();
        hasher.update(sec_key.as_bytes());
        hasher.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
        let expected = general_purpose::STANDARD.encode(hasher.finalize());
    
        if &expected != accept {
            return Err(ClientError::InvalidWebSocketUpgrade("'Sec-WebSocket-Accept' not match".into()));
        }
    
        Ok(Client { implement: WebSocketImpl::new(self.implement.stream) })
    }
}

impl HttpRequestBuilder {
    pub async fn send(self) -> Result<HttpResponse, ClientError> {
        let req = self.build()?;
    
        let host = req.headers.host.as_ref().ok_or(ClientBuildHttpRequest::MissingHost)?;
        let (host, port) = Client::parse_host_and_port(host)?;
        let addr = format!("{}:{}", host, port);
        let mut stream = TcpStream::connect(addr).await?;
    
        let bytes = req.to_bytes();
        stream.write_all(&bytes).await?;
        
        let response = HttpResponse::receive_from(&mut stream)
            .await?
            .ok_or(ClientError::ConnectionClosed)?;
        Ok(response)
    }
}

impl Client<HttpImpl> {
    fn parse_host_and_port(host: &str) -> Result<(&str, u16), ClientBuildHttpRequest>{
        if let Some((host_part, port_part)) = host.split_once(':') {
            let port = port_part.parse().map_err(|e| ClientBuildHttpRequest::InvalidHeaders(format!("invalid hots: {}", e)) )?;
            Ok((host_part, port))
        } else {
            Ok((host, 80))
        }
    }
}

#[cfg(test)]
mod client_tests {
    use crate::http::{HttpRequestBuilder, HttpResponse};

    #[tokio::test]
    async fn test_get_httpbin_get() {
        let res: HttpResponse = HttpRequestBuilder::get()
            .uri("http://httpbin.org/get?foo=bar").unwrap()
            .send()
            .await
            .unwrap();

        assert_eq!(res.line.status_code, 200);
        let body_str = String::from_utf8_lossy(&res.body.as_ref().unwrap().data);
        assert!(body_str.contains(r#""foo": "bar""#));
    }

    #[tokio::test]
    async fn test_post_json_httpbin_post() {
        let payload = serde_json::json!({ "name": "Rust", "lang": "中文" });
        let res = HttpRequestBuilder::post()
            .uri("http://httpbin.org/post").unwrap()
            .json(&payload).unwrap()
            .send()
            .await
            .unwrap();

        assert_eq!(res.line.status_code, 200);
        let parsed: serde_json::Value = serde_json::from_slice(&res.body.as_ref().unwrap().data).unwrap();
        assert_eq!(parsed["json"]["lang"], "中文");
        assert_eq!(parsed["json"]["name"], "Rust");
    }

    #[tokio::test]
    async fn test_custom_header_httpbin_headers() {
        let res = HttpRequestBuilder::get()
            .uri("http://httpbin.org/headers").unwrap()
            .header("X-Test-Header", "HelloClient")
            .send()
            .await
            .unwrap();

        assert_eq!(res.line.status_code, 200);
        let body = String::from_utf8_lossy(&res.body.as_ref().unwrap().data);
        assert!(body.contains(r#""X-Test-Header": "HelloClient""#));
    }
}
